/*
 * This is Melet, a salary slip designer. Feed it with data regarding to salary payment, prepare it for print through the graphical presentation, and you have it done.
 * Copyright (C) 2014, AFDiaX
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * This program is based on UMLet, a Free UML Tool for Fast UML Diagrams.
 * To contact the authors of UMLet visit https://code.google.com/p/umlet/people/list
 * To contact the authors of Melet visit https://gna.org/users/gabocze
 */
package com.baselet.element;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;

import com.baselet.control.SharedUtils;
import com.baselet.control.basics.geom.Rectangle;
import com.baselet.control.constants.SharedConstants;
import com.baselet.element.sticking.PointChange;
import com.baselet.element.sticking.Stickable;

public class UndoInformation {

	private final Rectangle diffRect;
	private final Map<Stickable, List<PointChange>> stickableMoves;
	private final String oldAdditionalAttributes;
	private final String newAdditionalAttributes;

	private UndoInformation(Rectangle diffRect, Map<Stickable, List<PointChange>> stickableMoves, String oldAdditionalAttributes, String newAdditionalAttributes) {
		this.diffRect = diffRect;
		this.stickableMoves = stickableMoves;
		this.oldAdditionalAttributes = oldAdditionalAttributes;
		this.newAdditionalAttributes = newAdditionalAttributes;
	}

	public UndoInformation(Rectangle newRect, Rectangle oldRect, Map<Stickable, List<PointChange>> stickableMoves, int gridSize, String oldAdditionalAttributes, String newAdditionalAttributes) {
		this(toMinZoom(newRect.subtract(oldRect), gridSize), stickableMoves, oldAdditionalAttributes, newAdditionalAttributes);
	}

	private static Map<Stickable, List<PointChange>> invertStickableMoves(Map<Stickable, List<PointChange>> stickableMoves) {
		Map<Stickable, List<PointChange>> invertedMap = new HashMap<Stickable, List<PointChange>>();
		for (Entry<Stickable, List<PointChange>> entry : stickableMoves.entrySet()) {
			List<PointChange> invList = new ArrayList<PointChange>();
			for (PointChange p : entry.getValue()) {
				invList.add(new PointChange(p.getIndex(), -p.getDiffX(), -p.getDiffY()));
			}
			invertedMap.put(entry.getKey(), invList);
		}
		return invertedMap;
	}

	public Rectangle getDiffRectangle(int gridSize, boolean undo) {
		Rectangle returnRect = undo ? diffRect.copyInverted() : diffRect;
		return toCurrentZoom(returnRect, gridSize);
	}

	public Map<Stickable, List<PointChange>> getStickableMoves(boolean undo) {
		return undo ? invertStickableMoves(stickableMoves) : stickableMoves;
	}

	public String getAdditionalAttributes(boolean undo) {
		if (undo) {
			return oldAdditionalAttributes;
		}
		else {
			return newAdditionalAttributes;
		}
	}

	private static Rectangle toMinZoom(Rectangle rectangle, int gridSize) {
		int xBefore = toMinZoom(rectangle.getX(), gridSize);
		int yBefore = toMinZoom(rectangle.getY(), gridSize);
		int wBefore = toMinZoom(rectangle.getWidth(), gridSize);
		int hBefore = toMinZoom(rectangle.getHeight(), gridSize);
		return new Rectangle(xBefore, yBefore, wBefore, hBefore);
	}

	private static int toMinZoom(int val, int gridSize) {
		return val / gridSize;
	}

	private static Rectangle toCurrentZoom(Rectangle rectangle, int gridSize) {
		int xBefore = toCurrentZoom(rectangle.getX(), gridSize);
		int yBefore = toCurrentZoom(rectangle.getY(), gridSize);
		int wBefore = toCurrentZoom(rectangle.getWidth(), gridSize);
		int hBefore = toCurrentZoom(rectangle.getHeight(), gridSize);
		return new Rectangle(xBefore, yBefore, wBefore, hBefore);
	}

	private static int toCurrentZoom(int val, int gridSize) {
		return val * gridSize;
	}

	public UndoInformation merge(UndoInformation other) {
		Rectangle mergedUndoDiffRect = diffRect.add(other.diffRect);
		Map<Stickable, List<PointChange>> mergedMap = new HashMap<Stickable, List<PointChange>>();
		mergeStickableMoves(mergedMap, stickableMoves);
		mergeStickableMoves(mergedMap, other.stickableMoves);
		return new UndoInformation(mergedUndoDiffRect, mergedMap, other.oldAdditionalAttributes, newAdditionalAttributes);

	}

	private void mergeStickableMoves(Map<Stickable, List<PointChange>> targetMap, Map<Stickable, List<PointChange>> sourceMap) {
		for (Entry<Stickable, List<PointChange>> sourceEntry : sourceMap.entrySet()) {
			Stickable sourceStickable = sourceEntry.getKey();
			List<PointChange> sourceChangeList = sourceEntry.getValue();
			List<PointChange> targetPointChanges = targetMap.get(sourceStickable);
			if (targetPointChanges == null) { // stickable was not moved before in targetMap
				targetMap.put(sourceStickable, sourceChangeList);
			}
			else { // stickable was already moved and these moves must be merged
				mergeSourceToTarget(sourceChangeList, targetPointChanges);
			}
		}
	}

	private void mergeSourceToTarget(List<PointChange> sourceChangeList, List<PointChange> targetPointChanges) {
		for (PointChange sourceChange : sourceChangeList) {
			mergePoint(targetPointChanges, sourceChange);
		}
	}

	private void mergePoint(List<PointChange> targetPointChanges, PointChange sourceChange) {
		for (ListIterator<PointChange> iter = targetPointChanges.listIterator(); iter.hasNext();) {
			PointChange targetChange = iter.next();
			if (sourceChange.getIndex().equals(targetChange.getIndex())) {
				iter.set(new PointChange(targetChange.getIndex(), sourceChange.getDiffX() + targetChange.getDiffX(), sourceChange.getDiffY() + targetChange.getDiffY()));
				return; // index already in targetList and successfully updated
			}
		}
		targetPointChanges.add(sourceChange); // index not in targetList, therefore added here
	}

	public String toString(boolean undo) {
		return "UndoInformation [diffRect=" + getDiffRectangle(SharedConstants.DEFAULT_GRID_SIZE, undo) + ", stickableMoves=" + SharedUtils.mapToString(getStickableMoves(undo)) + ", additionalAttributes=" + getAdditionalAttributes(undo) + "]";
	}

}
